#include "RenderMethodOpenGLES.h"

#include "YGError.h"
#include "YGLog.h"

#include "SurfaceOpenGLES.h"
#include "SpriteOpenGLES.h"
#include "ViewportOpenGLES.h"

#include "zip.h"

namespace YoghurtGum
{

	class FileStreamZip : public til::FileStream
	{

	public:

		FileStreamZip()
		{
			m_FilePath = new char[TIL_MAX_PATH];

			m_ByteMax = 8192;
			m_ByteCurrent = 0;
			m_ByteTotal = 0;
			m_Read = new til::byte[m_ByteMax];
		}
		~FileStreamZip()
		{
			delete m_FilePath;
			delete m_Read;
		}

		bool OpenZip(const char* a_Path, til::uint32 a_Options)
		{
			m_ZipPath = new char[TIL_MAX_PATH];
			strcpy(m_ZipPath, a_Path);

			m_Zip = zip_open(a_Path, 0, NULL);
			if (!m_Zip) 
			{ 
				Log::GetSingleton().Print("Could not find '%s'.", a_Path);
				return false; 
			}

			Log::GetSingleton().Print("Loaded zip '%s'.", a_Path);

			return true;
		}

		bool Open(const char* a_File, til::uint32 a_Options)
		{
			m_FilePath = new char[TIL_MAX_PATH];
			til::TIL_AddWorkingDirectory(m_FilePath, TIL_MAX_PATH, a_File);

			Log::GetSingleton().Print("Final path in zip: '%s'.", m_FilePath);

			m_File = zip_fopen(m_Zip, m_FilePath, 0);
			if (!m_File) { return false; }

			Log::GetSingleton().Print("Loaded file '%s'.", m_FilePath);

			// read at least one block first
			ReadBlock();

			return true;
		}

		bool Read(void* a_Dst, til::uint32 a_ElementSize, til::uint32 a_Count = 1)
		{
			return ReadByte((til::byte*)a_Dst, a_ElementSize * a_Count);
		}

		bool ReadByte(til::byte* a_Dst, til::uint32 a_Count = 1)
		{
			// the main function for reading data
			// we read with m_ByteMax bytes at a time from the zip file
			// if TinyImageLoader requests a byte not yet loaded, 
			// we load a new block of bytes

			for (til::uint32 i = 0; i < a_Count; i++)
			{
				*a_Dst++ = *m_Buffer++;

				if (++m_ByteCurrent == m_ByteTotal) 
				{ 
					if (!ReadBlock())
					{
						return false; 
					}
				}
			}

			return true;
		}

		bool ReadWord(til::word* a_Dst, til::uint32 a_Count = 1)
		{
			return ReadByte((til::byte*)a_Dst, 2 * a_Count);
		}

		bool ReadDWord(til::dword* a_Dst, til::uint32 a_Count = 1)
		{
			return ReadByte((til::byte*)a_Dst, 4 * a_Count);
		}

		bool Seek(til::uint32 a_Offset, til::uint32 a_Options)
		{
			if (a_Options & TIL_FILE_SEEK_CURR)
			{
				for (til::uint32 i = 0; i < a_Offset; i++)
				{
					if (++m_ByteCurrent == m_ByteTotal) 
					{ 
						if (!ReadBlock())
						{
							return false; 
						}
					}
				}
			}
			else
			{
				return false;
			}

			return true;
		}

		bool EndOfFile()
		{
			return m_EOF;
		}

		bool Close()
		{
			/*m_Error = unzCloseCurrentFile(m_ZipFile);*/
			//zip_fclose(m_File);
			return !CheckError();
		}

	private:

		bool CheckError()
		{
			/*if (m_Error != UNZ_OK)
			{
				return true;
			}*/

			return false;
		}

		bool ReadBlock()
		{	
			int found = zip_fread(m_File, m_Read, m_ByteMax);
			if (found < 0)
			{
				return false;
			}
			else if (found > 0)
			{
				m_Buffer = m_Read;
				m_ByteCurrent = 0;
				m_ByteTotal = found;
			}

			return true;
		}

		int m_ByteCurrent, m_ByteTotal;
		unsigned int m_ByteMax;
		til::byte* m_Read;
		til::byte* m_Buffer;

		bool m_EOF;

		char* m_ZipPath;
		char m_FileName[256];
		zip* m_Zip;
		zip_file* m_File;

		int m_Error;

	};

	RenderMethodOpenGLES* RenderMethodOpenGLES::s_Instance = NULL;

	RenderMethodOpenGLES::RenderMethodOpenGLES() : RenderMethod()
	{
		m_SpriteFirst = m_SpriteCurrent = NULL;
		m_TextureFirst = m_TextureCurrent = NULL;
	}

	RenderMethodOpenGLES::~RenderMethodOpenGLES()
	{
		for (TextureEntry* i = m_TextureFirst; i != NULL; i = i->next)
		{
			delete i->tex;
			delete i;
		}
	}

	void LoggingFunc(til::MessageData* a_Msg)
	{
		Log::GetSingleton().Print("%s", a_Msg->message);
	}

	bool RenderMethodOpenGLES::Init()
	{
		Log::GetSingleton().Print("RenderMethodOpenGLES::Init");

		til::TIL_Init();

		til::TIL_SetDebugFunc(LoggingFunc);
		til::TIL_SetErrorFunc(LoggingFunc);

		til::TIL_SetWorkingDirectory("assets/raw/", strlen("assets/raw/"));

		m_DeviceWidth = Device::Get().GetWidth();
		m_DeviceHeight = Device::Get().GetHeight();

		//Log::GetSingleton().Print("RenderMethodOpenGLES::Init - Dimensions: (%i, %i)", m_DeviceWidth, m_DeviceHeight);
		Log::GetSingleton().Print("RenderMethodOpenGLES::Init - Device Dimensions: (%i, %i)", Device::Get().GetWidth(), Device::Get().GetHeight());

		char* ext = (char*)glGetString(GL_EXTENSIONS);
		Log::GetSingleton().Print("Extensions: %s", ext); CGLE();

		int max_tex;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex);
		Log::GetSingleton().Print("Maximum texture size: %i", max_tex);

		m_NonPowerOfTwo = false;
		if (strstr(ext, "GL_ARB_texture_non_power_of_two"))
		{
			m_NonPowerOfTwo = true;
		}
		Log::GetSingleton().Print("Non power of two textures: %s", m_NonPowerOfTwo ? "YES" : "NO");

		m_ViewportTotal = 0;
		SetViewport(CreateViewport((uint32)m_DeviceWidth, (uint32)m_DeviceHeight));
		m_ViewportCurrent = m_ViewportList[0];
		
		/*glViewport(
			0, 0, 
			m_DeviceWidth, m_DeviceHeight
		); CGLE();
		glMatrixMode(GL_PROJECTION); CGLE();
			glLoadIdentity(); CGLE();
			glOrthox(
				0, GlIntToFixed(m_DeviceWidth), 
				GlIntToFixed(m_DeviceHeight), 0,
				GlIntToFixed(-1), GlIntToFixed(1)
			); CGLE();
		glMatrixMode(GL_MODELVIEW); CGLE();
			glLoadIdentity(); CGLE();*/

		m_ViewportCurrent->Project();

		// Initialize GL state.
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
		glEnable(GL_CULL_FACE);
		glShadeModel(GL_SMOOTH);
		glDisable(GL_DEPTH_TEST);

		m_ListFirst = NULL;

		return true;
	}

	void RenderMethodOpenGLES::Resize(int a_Width, int a_Height)
	{
		Log::GetSingleton().Print("RenderMethodOpenGLES: Resize (%i x %i)", a_Width, a_Height);

		m_DeviceWidth = a_Width;
		m_DeviceHeight = a_Height;

		m_ViewportCurrent->Resize((uint32)a_Width, (uint32)a_Height);

		/*glViewport(
			0, 0, 
			m_DeviceWidth, m_DeviceHeight
		); CGLE();
		glMatrixMode(GL_PROJECTION); CGLE();
			glLoadIdentity(); CGLE();
			glOrthox(
				0, GlIntToFixed(m_DeviceWidth), 
				GlIntToFixed(m_DeviceHeight), 0,
				GlIntToFixed(-1), GlIntToFixed(1)
			); CGLE();
		glMatrixMode(GL_MODELVIEW); CGLE();
			glLoadIdentity(); CGLE();*/

		for (TextureEntry* i = m_TextureFirst; i != NULL; i = i->next)
		{
			i->tex->Reload();
		}

		SpriteEntry* i = m_SpriteFirst;
		while (1)
		{
			if (!i) { break; }
			i->sprite->Reload();
			i = i->next;
		}
	}

	bool RenderMethodOpenGLES::Clear()
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	void RenderMethodOpenGLES::SetClearColor(Color a_Color)
	{
		m_Clear[0] = (GLclampf)a_Color.r;
		m_Clear[1] = (GLclampf)a_Color.g;
		m_Clear[2] = (GLclampf)a_Color.b;
		m_Clear[3] = 1.f;

		glClearColor(m_Clear[0], m_Clear[1], m_Clear[2], m_Clear[3]);
	}

	bool RenderMethodOpenGLES::PreRender()
	{
		m_ViewportCurrent->Enable();
		return true;
	}

	bool RenderMethodOpenGLES::PostRender()
	{
		m_ViewportCurrent->Disable();
		return true;
	}

	char* RenderMethodOpenGLES::GetName()
	{
		YG_STUB;
		return NULL;
	}

	unsigned long RenderMethodOpenGLES::GetFillColor(Color a_Color)
	{
		YG_STUB;
		return 0;
	}

	void RenderMethodOpenGLES::CleanUp()
	{
		YG_STUB;
	}

	void RenderMethodOpenGLES::SetFullScreen()
	{
		YG_STUB;
	}

	bool RenderMethodOpenGLES::LoadImage(const char* a_File, const char* a_Name, Color a_AlphaKey /*= Color::CNONE*/)
	{
		Log::GetSingleton().Print("Trying to load '%s' as '%s'...", a_File, a_Name);

		m_Stream = new FileStreamZip();
		m_Stream->OpenZip(FileManager::Get().GetWorkingDirectory(), TIL_FILE_ABSOLUTEPATH);
		m_Stream->Open(a_File, TIL_FILE_ABSOLUTEPATH);

		til::Image* loaded = til::TIL_Load(m_Stream, TIL_DEPTH_A8B8G8R8);
		if (!loaded)
		{
			Log::GetSingleton().Print("Could not load '%s'.", a_File);
			return false;
		}

		if (!m_ListFirst)
		{
			m_ListFirst = new DictionaryEntry;
			m_ListFirst->next = NULL;
			m_ListCurrent = m_ListFirst;
		}
		else
		{
			DictionaryEntry* temp = new DictionaryEntry;
			temp->next = NULL;
			m_ListCurrent->next = temp;
			m_ListCurrent = temp;
		}

		m_ListCurrent->image = loaded;
		m_ListCurrent->key = a_AlphaKey;
		strcpy(m_ListCurrent->name, a_Name);
		m_ListCurrent->next = NULL;

		return true;
	}

	RenderMethodOpenGLES::DictionaryEntry* RenderMethodOpenGLES::FindImage(const char* a_Name)
	{
		for (DictionaryEntry* i = m_ListFirst; i != NULL; i = i->next)
		{
			if (!strcmp(i->name, a_Name))
			{
				return i;
			}
		}

		return NULL;
	}

	Sprite* RenderMethodOpenGLES::CreateSprite(const char* a_Name)
	{
		Log::GetSingleton().Print("Trying to create sprite '%s'...", a_Name);

		DictionaryEntry* loaded = FindImage(a_Name);
		if (!loaded) 
		{ 
			Log::GetSingleton().Print("Could not find image named '%s'", a_Name);
			return NULL; 
		}

		if (!m_SpriteFirst)
		{
			m_SpriteFirst = new SpriteEntry;
			m_SpriteCurrent = m_SpriteFirst;
		}
		else
		{
			SpriteEntry* temp = new SpriteEntry;
			m_SpriteCurrent->next = temp;
			m_SpriteCurrent = temp;
		}
		m_SpriteCurrent->next = NULL;
		m_SpriteCurrent->sprite = new SpriteOpenGLES(GetTexture());
		m_SpriteCurrent->sprite->SetAlphaKey(loaded->key);
		m_SpriteCurrent->sprite->LoadImageData(loaded->image, a_Name);
		return m_SpriteCurrent->sprite;

		/*SpriteOpenGLES* result = new SpriteOpenGLES();		
		result->LoadImageData(loaded);
		return result;*/
	}

	SpriteEx* RenderMethodOpenGLES::CreateSpriteEx(const char* a_Name, uint32 a_Flags /*= YG_SPRITEEX_DEFAULT*/)
	{
		YG_STUB;
		return NULL;
	}

	Background* RenderMethodOpenGLES::CreateBackground(const char* a_Name, uint32 a_Flags /*= YG_BACKGROUND_DEFAULT*/)
	{
		YG_STUB;
		return NULL;
	}

	Surface* RenderMethodOpenGLES::CreateSurface(int a_Width, int a_Height, uint32 a_Flags /*= YG_SURFACE_DEFAULT*/)
	{
		Surface* result = new SurfaceOpenGLES(GetTexture(), a_Width, a_Height);
		return result;
	}

	uint32 RenderMethodOpenGLES::CreateViewport(uint32 a_Width, uint32 a_Height, uint32 a_Type /*= YG_VIEWPORT_DEFAULT*/)
	{
		Log::GetSingleton().Print("Current viewport: %i", m_ViewportTotal);
		m_ViewportList[m_ViewportTotal] = new ViewportOpenGLES(a_Width, a_Height, a_Type);
		return m_ViewportTotal++;
	}

	Viewport* RenderMethodOpenGLES::GetViewport(uint32 a_ID)
	{
		if (a_ID >= m_ViewportTotal) { return NULL; }
		return m_ViewportList[a_ID];
	}

	bool RenderMethodOpenGLES::SetViewport(uint32 a_ID)
	{
		if (a_ID >= m_ViewportTotal) { return false; }
		m_ViewportCurrent = m_ViewportList[a_ID];
		return true;
	}

	Surface* RenderMethodOpenGLES::GetRenderTarget()
	{
		YG_STUB;
		return NULL;
	}

	TextureOpenGLES* RenderMethodOpenGLES::GetTexture()
	{
		if (!m_TextureFirst)
		{
			m_TextureFirst = new TextureEntry;
			m_TextureCurrent = m_TextureFirst;
		}
		else
		{
			m_TextureCurrent->next = new TextureEntry;
			m_TextureCurrent = m_TextureCurrent->next;
		}

		m_TextureCurrent->tex = new TextureOpenGLES();
		m_TextureCurrent->next = NULL;

		return m_TextureCurrent->tex;
	}

	bool RenderMethodOpenGLES::NonPowerOfTwoTex()
	{
		return m_NonPowerOfTwo;
	}

	RenderMethodOpenGLES* RenderMethodOpenGLES::Get()
	{
		if (!s_Instance) { s_Instance = new RenderMethodOpenGLES(); }
		return s_Instance;
	}

	extern "C" 
	{ 
		RenderMethod* Install(Device* a_Device)
		{
			Device::SetInstance(a_Device);

			return ((RenderMethod*)RenderMethodOpenGLES::Get());
		}
	}

}